package com.pktechan.api.controller;


import com.pktechan.api.entity.CustBase;
import com.pktechan.api.entity.WxMp;
import com.pktechan.api.exception.CustomException;
import com.pktechan.api.service.CustBaseService;
import com.pktechan.api.service.MiniProgramService;
import com.pktechan.api.service.RedisService;
import com.pktechan.api.service.WxMpService;
import com.pktechan.api.utils.FastDfsHttpUtil;
import com.pktechan.api.utils.FileResponseData;
import com.pktechan.api.utils.HtmlUtil;
import org.apache.commons.codec.binary.Base64;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.OutputStream;
import java.util.Hashtable;
import java.util.Map;
import java.util.Set;

/**
 * Created by kaifabu on 2018/9/11.
 */
@RestController
@RequestMapping("miniProgramController")
public class MiniProgramController {

    private Logger logger = LoggerFactory.getLogger(MiniProgramController.class);
    @Autowired
    private MiniProgramService miniProgramService;
    @Autowired
    private WxMpService wxMpService;
    @Autowired
    private RedisService redisService;
    @Autowired
    private CustBaseService custBaseService;


    private static Integer lock_flag = 0;

    //注意，要使用线程安全的变量
    private Hashtable<String, Long> codeMap = new Hashtable<String, Long>();

    /**
     * 用户登录
     *
     * @param code
     * @param mpId
     * @param response
     */
    @RequestMapping("/onLogin")
    public void onLogin(String code, String mpId, HttpServletResponse response) {
        logger.debug("微信小程序用户登录,code:" + code + "---mpId:" + mpId);
        try {
            if (isAuthCode(code)) {
                logger.debug("code有效");
                String miniProgramSessionId = miniProgramService.onLogin(mpId, code);
                logger.debug("用户登录成功");
                HtmlUtil.writerSuccessJson(response, miniProgramSessionId);
            } else {
                HtmlUtil.writerFailJson(response, "登录失败");
                logger.debug("code无效");
            }
        }catch (CustomException e) {
            e.printStackTrace();
            logger.debug(e.getErrMsg());
            HtmlUtil.writerFailJson(response, e.getErrMsg());
        } catch (Exception e) {
            e.printStackTrace();
            HtmlUtil.writerFailJson(response, "登录失败");
        }
    }

    /**
     * 更新用户信息
     *
     * @param res
     * @param response
     */
    @RequestMapping("/updateUserInfo")
    public void updateUserInfo(@RequestParam  String res, @RequestParam String userLoginState,@RequestParam String mpId, HttpServletResponse response) {
        logger.debug("更新用户信息方法：res:" + res);
        try {
            miniProgramService.updateUserInfo(res, userLoginState, mpId);
            HtmlUtil.writerSuccessJson(response, userLoginState);
        }catch (CustomException e) {
            e.printStackTrace();
            logger.debug(e.getErrMsg());
            HtmlUtil.writerFailJson(response, e.getErrMsg());
        } catch (Exception e) {
            e.printStackTrace();
            HtmlUtil.writerFailJson(response, "登录失败");
        }
    }

    /**
     * 检查用户登录状态的有效性
     *
     * @param response
     */
    @RequestMapping("/checkUserLoginState")
    public void checkUserLoginState(String userLoginState, HttpServletResponse response) {
        try {
            Map respMap = redisService.getLoginState(userLoginState);
            if (respMap != null && respMap.containsKey("openid") && respMap.containsKey("unionid")) {
                HtmlUtil.writerSuccessJson(response, true);
            } else {
                HtmlUtil.writerSuccessJson(response, false);
            }
        }catch (CustomException e) {
            e.printStackTrace();
            logger.debug(e.getErrMsg());
            HtmlUtil.writerFailJson(response, e.getErrMsg());
        } catch (Exception e) {
            e.printStackTrace();
            HtmlUtil.writerFailJson(response, "检查失败");
        }
    }

    /**
     * 获取小程序码，适用于需要的码数量较少的业务场景。通过该接口生成的小程序码，永久有效，有数量限制，详见获取二维码。
     *
     * @param scene 最大32个可见字符，只支持数字，大小写英文以及部分特殊字符：!#$&'()*+,/:;=?@-._~，其它字符请自行编码为合法字符（因不支持%，中文无法使用 urlencode 处理，请使用其他编码方式）
     * @param page  必须是已经发布的小程序存在的页面（否则报错），例如 pages/index/index ,根路径前不要填加 /,不能携带参数（参数请放在scene字段里），如果不填写这个字段，默认跳主页面
     * @param mpId
     */
    @RequestMapping("/getWxACodeBase64")
    public void getWxACodeBase64(String scene, String page, String mpId, HttpServletResponse response, HttpServletRequest request) {
        try {
            byte[] codeByte = miniProgramService.getWxACode(scene, page, mpId);
            String codeBase64 = Base64.encodeBase64String(codeByte);
            HtmlUtil.writerSuccessJson(response, codeBase64);
        }catch (CustomException e) {
            e.printStackTrace();
            logger.debug(e.getErrMsg());
            HtmlUtil.writerFailJson(response, e.getErrMsg());
        } catch (Exception e) {
            e.printStackTrace();
            HtmlUtil.writerFailJson(response, "所传page页面不存在，或者小程序没有发布");
        }
    }

    /**
     * 取小程序码，适用于需要的码数量极多的业务场景。通过该接口生成的小程序码，永久有效，数量暂无限制。 更多用法详见 获取二维码。
     *
     * @param scene 最大32个可见字符，只支持数字，大小写英文以及部分特殊字符：!#$&'()*+,/:;=?@-._~，其它字符请自行编码为合法字符（因不支持%，中文无法使用 urlencode 处理，请使用其他编码方式）
     * @param page  必须是已经发布的小程序存在的页面（否则报错），例如 pages/index/index ,根路径前不要填加 /,不能携带参数（参数请放在scene字段里），如果不填写这个字段，默认跳主页面
     * @param mpId
     */
    @RequestMapping("/getWxACodeUnLimitBase64")
    public void getWxACodeUnLimitBase64(String scene, String page, String mpId, Integer width, HttpServletResponse response, HttpServletRequest request) {
        try {
            byte[] codeByte = miniProgramService.getWxACodeUnLimit(scene, page, mpId, width);
            String codeBase64 = Base64.encodeBase64String(codeByte);
            HtmlUtil.writerSuccessJson(response, codeBase64);
        }catch (CustomException e) {
            e.printStackTrace();
            logger.debug(e.getErrMsg());
            HtmlUtil.writerFailJson(response, e.getErrMsg());
        } catch (Exception e) {
            e.printStackTrace();
            HtmlUtil.writerFailJson(response, "所传page页面不存在，或者小程序没有发布");
        }
    }

    /**
     * 取小程序码，适用于需要的码数量极多的业务场景。通过该接口生成的小程序码，永久有效，数量暂无限制。 更多用法详见 获取二维码。
     *
     * @param scene 最大32个可见字符，只支持数字，大小写英文以及部分特殊字符：!#$&'()*+,/:;=?@-._~，其它字符请自行编码为合法字符（因不支持%，中文无法使用 urlencode 处理，请使用其他编码方式）
     * @param page  必须是已经发布的小程序存在的页面（否则报错），例如 pages/index/index ,根路径前不要填加 /,不能携带参数（参数请放在scene字段里），如果不填写这个字段，默认跳主页面
     * @param mpId
     */
    @RequestMapping("/getWxACodeUnLimit")
    public void getWxACodeUnLimit(String scene, String page, String mpId, HttpServletResponse response, HttpServletRequest request) {
        try {
            byte[] result = miniProgramService.getWxACodeUnLimit(scene, page, mpId, 450);
            ByteArrayInputStream in = new ByteArrayInputStream(result);
            BufferedImage image = ImageIO.read(in);
            String format = "jpg";
            OutputStream os = response.getOutputStream();
            ImageIO.write(image, format, os);
        }catch (CustomException e) {
            e.printStackTrace();
            logger.debug(e.getErrMsg());
            HtmlUtil.writerFailJson(response, e.getErrMsg());
        } catch (Exception e) {
            e.printStackTrace();
            HtmlUtil.writerFailJson(response, "所传page页面不存在，或者小程序没有发布");
        }
    }

    /**
     * 取小程序码，适用于需要的码数量极多的业务场景。通过该接口生成的小程序码，永久有效，数量暂无限制。 更多用法详见 获取二维码。
     *
     * @param scene 最大32个可见字符，只支持数字，大小写英文以及部分特殊字符：!#$&'()*+,/:;=?@-._~，其它字符请自行编码为合法字符（因不支持%，中文无法使用 urlencode 处理，请使用其他编码方式）
     * @param page  必须是已经发布的小程序存在的页面（否则报错），例如 pages/index/index ,根路径前不要填加 /,不能携带参数（参数请放在scene字段里），如果不填写这个字段，默认跳主页面
     * @param mpId
     */
    @RequestMapping("/getWxACodeUnLimitPath")
    public void getWXACodeUnLimitPath(Integer custId,String scene, String page, String mpId, Integer width, HttpServletResponse response, HttpServletRequest request) {
        try {
            CustBase custBase = custBaseService.getById(custId);
            if(custBase.getQrCodeUrl()!=null && !custBase.getQrCodeUrl().equals("")){
                HtmlUtil.writerSuccessJson(response, custBase.getQrCodeUrl());
            }else {
                byte[] result = miniProgramService.getWxACodeUnLimit(scene, page, mpId, width);
                FileResponseData fileResponseData = FastDfsHttpUtil.uploadFile(result, "scene.jpg");
                custBase.setQrCodeUrl(fileResponseData.getHttpUrl());
                custBaseService.update(custBase);
                HtmlUtil.writerSuccessJson(response, custBase.getQrCodeUrl());
            }
        }catch (CustomException e) {
            e.printStackTrace();
            logger.debug(e.getErrMsg());
            HtmlUtil.writerFailJson(response, e.getErrMsg());
        } catch (Exception e) {
            e.printStackTrace();
            HtmlUtil.writerFailJson(response, "所传page页面不存在，或者小程序没有发布");
        }
    }

    /**
     * 根据MpId获取accessToken
     *
     * @param mpId
     * @param response
     */
    @RequestMapping("/getAccessTokenByMpId")
    public void getAccessTokenByMpId(String mpId, HttpServletResponse response) {
        try {
            WxMp wxMp = wxMpService.getByMpId(mpId);
            String accessToken = wxMpService.getMpAccessToken(wxMp);
            HtmlUtil.writerSuccessJson(response, accessToken);
        } catch (CustomException e) {
            e.printStackTrace();
            HtmlUtil.writerFailJson(response, e.getErrMsg());
        }
    }

    //因企鹅平台的bug,短时间内
    private boolean isAuthCode(String code) {
        if (codeMap.get(code) == null) {
            logger.debug("new code," + codeMap.size() + ",hashCode:");
            codeMap.put(code, System.currentTimeMillis());
            synchronized (MiniProgramController.lock_flag) {
                if (codeMap.size() > 200) {
                    flushCodeMap(5);
                }
            }
            return true;
        } else {
            long codeTime = codeMap.get(code);
            long ms = System.currentTimeMillis() - codeTime;
            logger.debug("old code, had user at " + ms + "ms ");
            return false;
        }
    }

    //防内存泄露
    private void flushCodeMap(int keepSecends) {
        Long curTime = System.currentTimeMillis();
        Hashtable<String, Long> newCodeMap = new Hashtable<String, Long>();
        Set<String> codeKey = codeMap.keySet();
        logger.debug("snsCodeMap size:" + codeMap.size() + ",will do flush! hashCode:" + codeMap.hashCode());
        for (String code : codeKey) {
            Long codeTime = codeMap.get(code);
            logger.debug("code=" + code + ",curTime=" + curTime + ",codeTime=" + codeTime);
            if (curTime - codeTime < keepSecends * 1000) {
                newCodeMap.put(code, codeTime);
            }
        }
        codeMap = newCodeMap;
        logger.debug("flush done, snsCodeMap size:" + codeMap.size() + ",hashCode:" + codeMap.hashCode());
    }
}
